package com.xci.core.internal;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xci.core.base.GMap;
import com.xci.core.base.RestMessage;
import com.xci.core.domain.*;
import com.xci.core.exceptions.ValidException;
import com.xci.core.helper.*;
import com.xci.core.setting.SysSetting;
import com.xci.sys.entity.*;
import com.xci.sys.service.*;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Validator;
import javax.validation.constraints.NotEmpty;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 系统操作类
 * @author 吕艳阳
 */
@Slf4j
public final class Sys {
    @Resource
    private AppService appService;
    @Resource
    private UserService userService;
    @Resource
    private DeptService deptService;
    @Resource
    private RoleService roleService;
    @Resource
    private ParamService paramService;
    @Resource
    private DicService dicService;
    @Resource
    private ModuleService moduleService;
    @Resource
    private OperateLogService operateLogService;//操作日志服务
    @Resource
    private HistoryLogService historyService;//历史日志服务
    @Resource
    private Validator beanValidator;//bean验证对象
    private static Sys instance;
    private static SysSetting setting;
    public static final String IP_URL = "http://ip.taobao.com/service/getIpInfo.php";

    @PostConstruct
    private void init() {
        Sys.instance = this;
    }

    /**
     * 获取Sys实例对象
     */
    public static Sys me() {
        return Sys.instance;
    }

    /**
     * 设置系统配置对象
     */
    public static void setting(SysSetting setting) {
        Sys.setting = setting;
    }

    /**
     * 获取系统配置对象
     */
    public static SysSetting setting() {
        return setting;
    }

    /**
     * 当前操作用户
     */
    public SysUser getCurrentUser() {
        HttpServletRequest request = WebHelper.getRequest();
        if (request == null) {
            return null;
        }
        Object userObj;
        if (WebHelper.isApp(request)) {
            userObj = request.getAttribute(Const.CURRENT_USER_API_KEY);
        } else {
            userObj = request.getSession().getAttribute(Const.CURRENT_USER_Session_KEY);
        }
        if (userObj != null) {
            return (SysUser) userObj;
        }
        return null;
    }

    /**
     * 获取用户对象
     * @param userId 用户主键
     */
    public SysUser getUser(String userId) {
        SysUser user = userService.selectById(userId);
        if (user == null) {
            throw new ValidException("无效的用户主键");
        }
        return user;
    }

    /**
     * 获取当前应用
     */
    public SysApp getCurrentApp() {
        HttpServletRequest request = WebHelper.getRequest();
        SysApp app = null;
        if (WebHelper.isApp(request)) {
            Object appObj = request.getAttribute(Const.CURRENT_APP_API_KEY);
            if (appObj != null) {
                app = (SysApp) appObj;
            }
        } else {
            var webAppId = setting.getWeb().getAppId();
            var appObj = getApp(webAppId);
            if (appObj != null) {
                app = appObj;
            }
        }

        if (app == null) {
            throw new ValidException("无效的Web应用");
        }
        return app;
    }

    /**
     * 获取当前应用
     */
    public SysApp getApp(String appId) {
        if (Helper.isBlank(appId)) return null;
        return appService.selectById(appId);
    }

    /**
     * 生成用户Token
     */
    public String buildUserToken(String userId) {
        GMap map = GMap.newMap(Const.USER_ID, Helper.encrypt(userId));
        Date expireDate = DateUtil.date().offset(DateField.DAY_OF_MONTH, 30);
        return JwtHelper.create(map, Const.JWT_SECRET_KEY, expireDate);
    }

    /**
     * 设置用户登录cookie
     */
    public void setUserJwtCookie(SysUser entity) {
        String userId = entity.getId();
        GMap map = GMap.newMap(Const.USER_ID, Helper.encrypt(userId));
        Date expireDate = DateUtil.date().offset(DateField.DAY_OF_MONTH, 7);
        String token = JwtHelper.create(map, Const.JWT_SECRET_KEY, expireDate);
        Cookie cookie = new Cookie(Const.CURRENT_USER_COOKIE_KEY, token);
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        //7天
        cookie.setMaxAge(60 * 60 * 24 * 7);
        CookieHelper.setCookie(cookie);
    }

    /**
     * 根据token获取用户对象
     */
    public RestMessage<SysUser> getUserByToken(String token) {
        Map<String, Object> map = JwtHelper.parse(token, Const.JWT_SECRET_KEY);
        Object userIdObj = map.get(Const.USER_ID);
        if (userIdObj == null || userIdObj.toString().length() == 0) {
            return RestMessage.fail("非法用户,信息已被篡改");
        }
        String userId = Helper.decrypt(userIdObj.toString());
        if (!NumberUtil.isLong(userId)) {
            return RestMessage.fail("非法用户,信息已被篡改");
        }
        UserService userService = SpringHelper.getBean(UserService.class);
        SysUser userEntity = userService.selectById(userId);
        if (userEntity == null) {
            return RestMessage.fail("非法用户,无效的用户标识");
        }
        return RestMessage.success(userEntity);
    }

    /**
     * 检查登录状态,并自动登录
     */
    public boolean checkAndAutoLogin(HttpServletRequest request) {
        Object user = request.getSession().getAttribute(Const.CURRENT_USER_Session_KEY);
        if (user != null) {
            return true;
        }
        String userToken = CookieHelper.getCookie(Const.CURRENT_USER_COOKIE_KEY);
        if (userToken == null) {
            return false;
        }
        var userResult = getUserByToken(userToken);
        if (userResult.isSuccess()) {
            RestMessage sresult = onLoginSuccess(userResult.getData());
            return sresult.isSuccess();
        }
        return false;
    }

    /**
     * 用户登录成功函数
     */
    public RestMessage onLoginSuccess(SysUser entity) {
        if (!entity.getStatus()) {
            return RestMessage.fail("账号已被禁用");
        }
        var request = WebHelper.getRequest();
        request.getSession().setAttribute(Const.CURRENT_USER_Session_KEY, entity);
        return RestMessage.success();
    }

    /**
     * 设置实体用户字段值
     * @param entity  实体对象
     * @param created 是否新建
     */
    public void setUserInfo(Object entity, boolean created) {
        if (entity == null) return;
        SysUser user = getCurrentUser();
        Date date = new Date();
        if (entity instanceof BaseUserEntity) {
            BaseUserEntity baseEntity = (BaseUserEntity) entity;
            if (created) {
                baseEntity.setCreateDateTime(date);
            } else {
                baseEntity.setUpdateDateTime(date);
            }
            if (user != null) {
                if (created) {
                    baseEntity.setCreateUserId(user.getId());
                    baseEntity.setCreateUserName(user.getName());
                } else {
                    baseEntity.setUpdateUserId(user.getId());
                    baseEntity.setUpdateUserName(user.getName());
                }
            }
        } else if (entity instanceof BaseOperateUserEntity) {
            BaseOperateUserEntity baseEntity = (BaseOperateUserEntity) entity;
            baseEntity.setOperateDateTime(date);
            if (user != null) {
                baseEntity.setOperateUserId(user.getId());
                baseEntity.setOperateUserName(user.getName());
            }
        } else {
            setUserFieldValue(entity, user, "OperateUserId", "OperateUserName", "OperateDateTime");
            if (created) {
                setUserFieldValue(entity, user, "CreateUserId", "CreateUserName", "CreateDateTime");
            } else {
                setUserFieldValue(entity, user, "UpdateUserId", "UpdateUserName", "UpdateDateTime");
            }
        }
    }

    /**
     * 设置实体用户字段值
     * @param entity    实体对象
     * @param user      用户对象
     * @param idField   用户主键字段
     * @param nameField 用户姓名字段
     * @param timeField 日期时间字段
     */
    public void setUserFieldValue(Object entity, SysUser user, String idField, String nameField,
                                  String timeField) {
        Method method = ReflectUtil.getMethodByName(entity.getClass(), "set" + timeField);
        if (method != null) {
            try {
                method.invoke(entity, new Date());
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        if (user != null) {
            method = ReflectUtil.getMethodByName(entity.getClass(), "set" + idField);
            if (method != null) {
                try {
                    method.invoke(entity, user.getId());
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
            method = ReflectUtil.getMethodByName(entity.getClass(), "set" + nameField);
            if (method != null) {
                try {
                    method.invoke(entity, user.getName());
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 构建操作日志对象
     * @param logInfo 日志信息
     */
    public SysOperateLog buildOperateLog(LogInfo logInfo) {
        SysOperateLog operateLog = new SysOperateLog();
        operateLog.setId(Helper.nextIdStr());
        operateLog.setModule(logInfo.getModule());
        operateLog.setMsg(logInfo.getMsg());
        operateLog.setCostTime(logInfo.getCostTime());
        operateLog.setStatus(logInfo.isStatus());
        operateLog.setMethod(logInfo.getMethod());
        SysApp app = getCurrentApp();
        operateLog.setAppId(app.getId());
        operateLog.setAppName(app.getName());
        operateLog.setOperateDateTime(new Date());
        SysUser user = getCurrentUser();
        if (user != null) {
            operateLog.setOperateDeptId(user.getDeptId());
            operateLog.setOperateDeptName(user.getDeptName());
            operateLog.setOperateUserId(user.getId());
            operateLog.setOperateUserName(user.getName());
        }

        HttpServletRequest request = WebHelper.getRequest();
        if (request != null) {
            operateLog.setReqParam(JsonHelper.serialize(WebHelper.getRequestParamMap()));
            operateLog.setReqUrl(request.getRequestURI());
            operateLog.setReqType(request.getMethod());
            if (request.getHeader("User-Agent") != null) {
                UserAgent agent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
                String bver = Const.EMPTY;
                if (agent.getBrowserVersion() != null) {
                    bver = agent.getBrowserVersion().getMajorVersion();
                }
                operateLog.setBrowser(Helper.format("{} {}", agent.getBrowser().getName(), bver));
                operateLog.setOs(agent.getOperatingSystem().getName());
            }
            // String ip = ServletUtil.getClientIP(request);
            // operateLog.setIpAddress(ip);
            // operateLog.setIpLocation(resolvingIpLocation(ip));
        }
        return operateLog;
    }

    /**
     * 构建登陆日志对象
     */
    public SysLoginLog buildLoginLog(String account, boolean status, String msg) {
        SysLoginLog loginLog = new SysLoginLog();
        loginLog.setId(Helper.nextIdStr());
        loginLog.setAccount(account);
        loginLog.setStatus(status);
        loginLog.setMsg(msg);
        loginLog.setOperateDateTime(new Date());
        SysApp app = getCurrentApp();
        loginLog.setAppId(app.getId());
        loginLog.setAppName(app.getName());

        HttpServletRequest request = WebHelper.getRequest();
        if (request != null) {
            if (request.getHeader("User-Agent") != null) {
                UserAgent agent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
                String bver = Const.EMPTY;
                if (agent.getBrowserVersion() != null) {
                    bver = agent.getBrowserVersion().getMajorVersion();
                }
                loginLog.setBrowser(Helper.format("{} {}", agent.getBrowser().getName(), bver));
                loginLog.setOs(agent.getOperatingSystem().getName());
            }
            String ip = ServletUtil.getClientIP(request);
            loginLog.setIpAddress(ip);

            //解析ip地址地理位置
            if (setting().getApp().isSysApiResolvingIpLocation()) {
                loginLog.setIpLocation(resolvingIpLocation(ip));
            }
        }
        return loginLog;
    }

    /**
     * 根据异常对象构建错误消息
     * @param e 异常对象
     */
    public SysErrorLog buildExceptionLog(Exception e) {
        SysErrorLog errorLog = new SysErrorLog();
        errorLog.setId(Helper.nextIdStr());
        errorLog.setMsg(e.getMessage());
        var request = WebHelper.getRequest();
        if (request != null) {
            errorLog.setReqParam(JsonHelper.serialize(WebHelper.getRequestParamMap()));
            errorLog.setReqUrl(request.getRequestURI());
        }
        SysUser user = getCurrentUser();
        if (user != null) {
            errorLog.setOperateUser(Helper.format("{}({})-{}", user.getAccount(), user.getName(), user.getId()));
        }
        errorLog.setOperateDateTime(new Date());
        StringWriter stringWriter = new StringWriter();
        e.printStackTrace(new PrintWriter(stringWriter));
        errorLog.setDetails(stringWriter.toString());
        return errorLog;
    }

    /**
     * 获取当前用户角色字符串
     */
    public String getRoleName(SysUser user) {
        if (user.getIsAdmin()) {
            return "系统管理员";
        }
        return roleService.selectByUserId(user.getId()).getName();
        // List<SysRole> roleList = roleService.selectListByUserId(userEntity.getUserId());
        // return roleList.stream().map(SysRole::getRoleName).collect(Collectors.joining(","));
    }

    /**
     * 根据参数编码获取参数值
     * @param code         参数编码
     * @param defaultValue 找不到参数时返回的默认值
     * @return 返回参数编码对应的参数值
     */
    public String getParamValue(@NotEmpty(message = "请指定参数编码") String code, String defaultValue) {
        return paramService.selectValueByCode(code, defaultValue);
    }

    /**
     * 根据字典编码获取字典明细列表
     * @param dicCode 字典编码
     */
    public List<SysDic> getDicList(String dicCode) {
        DicService dicService = SpringHelper.getBean(DicService.class);
        return dicService.selectListByCode(dicCode);
    }

    /**
     * 查询数据字典键值对
     * @param dicCode     字典类型编码
     * @param value       字典项值
     * @param defaultName 找不到指定的项值时返回的默认名称
     * @return 返回数据字典键值对
     */
    public String getDicNameByValue(String dicCode, String value, String defaultName) {
        DicService dicService = SpringHelper.getBean(DicService.class);
        return dicService.selectNameByValue(dicCode, value, defaultName);
    }

    /**
     * 查询数据字典键值对
     * @param dicCode      字典类型编码
     * @param name         字典名称
     * @param defaultValue 找不到指定的项值时返回的默认值
     * @return 返回数据字典键值对
     */
    public String getDicValueByName(String dicCode, String name, String defaultValue) {
        DicService dicService = SpringHelper.getBean(DicService.class);
        return dicService.selectValueByName(dicCode, name, defaultValue);
    }

    /**
     * 是否有指定模块编码的权限
     * @param user  用户对象
     * @param codes 模块编码字符串
     */
    public Boolean isAuthorize(SysUser user, String codes) {
        return moduleService.isAuthorize(user, codes);
    }

    /**
     * 手动验证Bean对象
     * @param bean bean对象
     */
    public BoolMessage validBean(Object bean) {
        StringBuilder builder = new StringBuilder();
        var result = beanValidator.validate(bean);
        if (result.size() > 0) {
            for (var item : result) {
                String field = item.getPropertyPath().toString();
                String msg = item.getMessage();
                builder.append(Helper.getValidMsg(field, msg));
            }
            return BoolMessage.fail(builder.toString());
        }
        return BoolMessage.success();
    }

    /**
     * 保存系统操作日志
     * @param logInfo 日志信息
     */
    public void saveLog(LogInfo logInfo) {
        SysOperateLog log = Sys.me().buildOperateLog(logInfo);
        operateLogService.insert(log);
    }

    /**
     * 保存新建历史记录
     * @param history 历史信息
     */
    public void saveInsertHistory(HistoryInfo history) {
        historyService.saveInsert(history);
    }

    /**
     * 保存修改历史记录
     * @param history 历史信息
     */
    public void saveUpdateHistory(HistoryInfo history) {
        historyService.saveUpdate(history);
    }

    /**
     * 保存删除历史记录
     * @param history 历史信息
     */
    public void saveDeleteHistory(HistoryInfo history) {
        historyService.saveDelete(history);
    }

    /**
     * 上传文件
     * @param file 上传文件对象
     * @return 返回上传文件信息
     */
    public RestMessage<UploadInfo> uploadFile(MultipartFile file) {
        return uploadFile(file, null);
    }

    /**
     * 上传文件
     * @param file 上传文件对象
     * @param path 文件存储路径
     * @return 返回上传文件信息
     */
    public RestMessage<UploadInfo> uploadFile(MultipartFile file, String path) {
        if (file == null || file.getSize() == 0) {
            return RestMessage.fail("请指定要上传的文件");
        }
        var setting = Sys.setting().getUpload();
        if (setting.getAllowMaxSize() <= 0) {
            return RestMessage.fail("请指定允许文件上传的最大文件大小");
        }
        if (setting.getAllowExtension() == null || setting.getAllowExtension().length == 0) {
            return RestMessage.fail("请指定允许文件上传的扩展名");
        }

        String originalFilename = file.getOriginalFilename();
        if (Helper.isNotBlank(originalFilename)) {
            originalFilename = originalFilename.replace("&", "").replace("?", "");
        }

        String extName = FileUtil.extName(originalFilename);
        if (Helper.isBlank(FileUtil.mainName(originalFilename))) {
            return RestMessage.fail("请指定上传文件的文件名");
        }
        if (Helper.isBlank(extName)) {
            return RestMessage.fail("请指定上传文件的扩展名");
        }

        extName = extName.toLowerCase();
        long allowFileMaxSize = setting.getAllowMaxSize() * 1024;
        if (file.getSize() > allowFileMaxSize) {
            String msg = Helper.format("上传文件大小超过限制,系统限制最大为{}", FileUtil.readableFileSize(allowFileMaxSize));
            return RestMessage.fail(msg);
        }
        String finalExtName = extName;
        if (Arrays.stream(setting.getAllowExtension()).noneMatch(p -> p.toLowerCase().equals(finalExtName))) {
            //            String msg = Sh.format("上传文件扩展名禁止。只允许{}格式", String.join(",", setting.getAllowExtension()));
            return RestMessage.fail("上传文件扩展名禁止。");
        }

        if (Helper.isBlank(path)) {
            path = Helper.format("/{}/{}.{}", DateHelper.format(new Date(), "yyyyMMdd"), Helper.guid(), extName);
        }

        // String uploadFolder = setting.getRoot();
        // if(!uploadFolder.startsWith("/")) {
        //     uploadFolder = "/" + uploadFolder;
        // }
        // String virtualFileName = setting.getFileName();
        // if(Sh.isBlank(virtualFileName)) {
        //     virtualFileName = Sh.format("{}.{}", Sh.guid(), extName);
        // }
        // Path virtualFilePath = Paths.get(uploadFolder, virtualFileName);

        File saveFileObject = buildFile(path);
        try {
            // if(saveFileObject.exists()){
            //     return new BoolMessage<>(false, "指定的文件名称已存在");
            // }else {
            //     FileUtil.mkParentDirs(saveFileObject);
            //     FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(saveFileObject));
            // }
            FileUtil.mkParentDirs(saveFileObject);
            FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(saveFileObject));
        } catch (IOException e) {
            return RestMessage.fail(e.getMessage());
        }

        UploadInfo info = new UploadInfo();
        info.setName(originalFilename);
        info.setContentType(file.getContentType());
        info.setUrl(path);
        info.setSize(file.getSize());
        return RestMessage.success(info);
    }

    /**
     * 解析ip地址地理位置
     * @param ip ip地址
     */
    public static String resolvingIpLocation(String ip) {
        String address = "";
        if (Helper.isBlank(ip)) {
            return address;
        }
        if (!setting.getApi().isResolvingIpLocation()) {
            return address;
        }
        // 内网不查询
        if (isInternalIP(ip)) {
            return "内网IP";
        }

        var url = IP_URL + "?ip=" + ip;
        String rspStr = HttpUtil.get(url, 3000);
        if (StringUtils.isEmpty(rspStr)) {
            return address;
        }
        try {
            JSONObject obj = JSONUtil.parseObj(rspStr);
            JSONObject data = obj.get("data", JSONObject.class);
            String country = data.getStr("country");
            String region = data.getStr("region");
            String city = data.getStr("city");
            String isp = data.getStr("isp");
            address = Helper.format("{} {}{} {}", country, region, city, isp);
        } catch (Exception e) {
            log.error("解析ip出现错误,请求字符串:{},返回的字符串:{}", url, rspStr);
        }
        return address;
    }

    /**
     * 是否内网 ip
     * @param ipAddress ip 地址
     */
    public static boolean isInternalIP(String ipAddress) {
        if (ipAddress.equals("0:0:0:0:0:0:0:1") || ipAddress.equals("localhost") || ipAddress.equals("127.0.0.1")) {
            return true;
        }
        boolean isInnerIp;
        long ipNum = getIpNum(ipAddress);
        /*
         私有IP：A类  10.0.0.0-10.255.255.255
         B类  172.16.0.0-172.31.255.255
         C类  192.168.0.0-192.168.255.255
         当然，还有127这个网段是环回地址
         **/
        long aBegin = getIpNum("10.0.0.0");
        long aEnd = getIpNum("10.255.255.255");
        long bBegin = getIpNum("172.16.0.0");
        long bEnd = getIpNum("172.31.255.255");
        long cBegin = getIpNum("192.168.0.0");
        long cEnd = getIpNum("192.168.255.255");
        isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd);
        return isInnerIp;
    }

    private static long getIpNum(String ipAddress) {
        String[] ip = ipAddress.split("\\.");
        long a = Integer.parseInt(ip[0]);
        long b = Integer.parseInt(ip[1]);
        long c = Integer.parseInt(ip[2]);
        long d = Integer.parseInt(ip[3]);

        return a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d;
    }

    private static boolean isInner(long userIp, long begin, long end) {
        return (userIp >= begin) && (userIp <= end);
    }


    /**
     * 生成上传文件相对路径
     * @param path 路径
     */
    public static String buildPath(String path) {
        return setting.getUpload().buildPath(path);
    }

    /**
     * 生成上传文件对象
     * @param path 路径
     */
    public static File buildFile(String path) {
        String filePath = buildPath(path);
        return FileHelper.buildRootFile(filePath);
    }
}